package org.hilo.boot.core.shiro;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * 
 * @author zollty
 * @since 2017-07-02
 */
public class RedisSessionCacheService implements SessionCacheService {
    private static final Logger log = LoggerFactory.getLogger(RedisSessionCacheService.class);
    private static final String REDIS_SHIRO_SESSION = "shiro-ses.";
    private String redisShiroSessionPrefix = REDIS_SHIRO_SESSION;
    private long redisShiroSessionTimeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT;

    private RedisTemplate<String, Session> redisTemplate;
    
    @Override
    public void saveSession(Session session) {
        if (session == null || session.getId() == null) {
            return;
        }
        try {
            long timeout = getTimeout4Redis(session);
            if (timeout > 3000) { // 小于3秒就没必要保存redis了
                redisTemplate.opsForValue().set(buildKey(session.getId()), 
                        session, timeout, TimeUnit.MILLISECONDS);
            }
        } catch (Exception e) {
            log.error("save session error.", e);
        }
    }

    @Override
    public void updateSession(Session session) {
        if (session == null || session.getId() == null) {
            throw new NullPointerException("session is empty");
        }
        try {
            long timeout = getTimeout4Redis(session);
            redisTemplate.boundValueOps(buildKey(session.getId()))
                         .set(session, timeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("update session error.", e);
        }
    }

    @Override
    public void refreshSession(Session session) {
        long timeout = getTimeout4Redis(session);
        try {
            redisTemplate.expire(buildKey(session.getId()), timeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("update session error.", e);
        }
    }

    @Override
    public void deleteSession(Serializable sessionId) {
        if (sessionId == null) {
            throw new NullPointerException("session id is empty");
        }
        try {
            redisTemplate.delete(buildKey(sessionId));
        } catch (Exception e) {
            log.error("delete session error.", e);
        }
    }

    @Override
    public Session getSession(Serializable sessionId) {
        if (sessionId == null) {
            throw new NullPointerException("session id is empty");
        }
        Session session = null;
        try {
            session = redisTemplate.boundValueOps(buildKey(sessionId)).get();
        } catch (Exception e) {
            log.error("get session error.", e);
        }
        return session;
    }

    private String buildKey(Serializable sessionId) {
        return redisShiroSessionPrefix + sessionId;
    }

    private long getTimeout4Redis(Session session) {
        return session.getTimeout() > 0 ? session.getTimeout() : redisShiroSessionTimeout;
    }

    public RedisTemplate<String, Session> getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<String, Session> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /** 保存到Redis中key的前缀 prefix+sessionId */
    public void setRedisShiroSessionPrefix(String redisShiroSessionPrefix) {
        this.redisShiroSessionPrefix = redisShiroSessionPrefix;
    }

    /** 设置会话的过期时间 */
    public void setRedisShiroSessionTimeout(Long redisShiroSessionTimeout) {
        if (redisShiroSessionTimeout != null) {
            this.redisShiroSessionTimeout = redisShiroSessionTimeout + 500;
        }
    }
    
    public void setAppName(String appName) {
        if (appName != null && appName.trim().length() > 0) {
            this.redisShiroSessionPrefix = appName.trim() + "." + this.redisShiroSessionPrefix;
        } else {
            log.warn("redis session enabled but not set app prefix for redis key.");
        }
    }
}
